Skip to content

feat(ai, adapters): a ./models catalog in each provider package - #1887

Closed
tombeckenham wants to merge 5 commits into
modelschemas-for-updatesfrom
feat/adapter-model-catalogs
Closed

tombeckenham wants to merge 5 commits into
modelschemas-for-updatesfrom
feat/adapter-model-catalogs

Conversation

@tombeckenham

@tombeckenham tombeckenham commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Apps can now read a provider's models at runtime from that provider's package, for example import { models } from '@tanstack/ai-anthropic/models'. Each record has the context window, max output tokens, prices, input kinds, and reasoning levels. modelCost(model, usage) from @tanstack/ai prices one call. This PR supersedes #1772 and #1791.

Stacked on #1308. Review only the commits above it. #1308 adds the modelschemas client and the native model-meta.ts sync. This PR adds the runtime catalogs on top.

🎯 Changes

  • A ./models subpath in 11 provider packages. There are 13 catalogs and about 1,380 models. ai-openai also has ./azure-models, and ai-cloudflare also has ./gateway-models.
  • @tanstack/ai adds the ModelRecord type and modelCost. modelCost takes the TokenUsage from onUsage, uses long-context tiers, and prices 1-hour cache writes at their own rate. It returns undefined when a price it needs is not known.
  • One source: modelschemas. scripts/sync-model-catalogs.ts makes one listModels call (GET /v1/models?activity=chat&pricing=1) through @modelschemas/client, and writes each catalog file. Rows use the client's Model type. The client comes from feat(model-sync): sync native models from modelschemas only, with no prices in model metadata #1308's createSyncClient, so MODELSCHEMAS_API_KEY applies. It runs with node. The daily sync workflow has a new step for it, and the sync writes a patch changeset for each package whose models changed.
  • No guessed values. A value modelschemas does not list stays undefined: prices, a single rate, limits, input kinds, and reasoning levels. An unknown effort value, input kind, or price operator throws. Reasoning levels come from modelschemas' own level map, or from its effort list.
  • Docs. The new page docs/advanced/model-catalog.md is for app developers who build a model picker or show the cost of a run. The prompt-caching page links to it, and the agent skills point to the subpaths.

What changed from #1772 and #1791, as asked on #1772:

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with pnpm run test:pr, or these tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.
  • Docs: I updated docs/ for this change, or this change is not user-facing.
  • Changeset: I added a changeset (pnpm changeset), or this PR does not change a published package.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Testing

Commands run.

  • node scripts/sync-model-catalogs.ts through @modelschemas/client 0.2.1: it refreshed 5 catalogs and skipped the 8 with no change. A second build from the same data compared equal to the formatted files, so a sync with no change writes nothing.
  • vitest run scripts/model-sync: 9 files, 88 tests passed, including feat(model-sync): sync native models from modelschemas only, with no prices in model metadata #1308's. CI does not run this folder (test:maintainer only).
  • vitest run tests/model-catalog.test.ts in packages/ai: 3 passed.
  • tsc --noEmit in packages/ai: no errors. In ai-openai, ai-openrouter, ai-cloudflare, and ai-bedrock: no errors in the catalog files.
  • oxlint --type-aware on the new source files: clean.
  • Not run: pnpm test:pr and the E2E suite. CI runs them. No E2E spec is added, because the catalog is static data plus one pure function with no wire behavior.

Manual test.

  1. Run node scripts/sync-model-catalogs.ts. Expect one line per catalog file. Only data that changed upstream since this commit shows in git status.
  2. Run pnpm --filter @tanstack/ai test:lib -- model-catalog.
  3. Open packages/ai-anthropic/src/models.ts. claude-sonnet-4-5 has cacheWrite1h: 6 and no reasoning levels, because modelschemas lists only mode: 'budget' for it.

How this PR makes testing easy. scripts/model-sync/model-catalog.test.ts covers the row rules: level map, efforts, unknown values that throw, prices from a price expression with tiers and currency, aliases, and repeated ids. packages/ai/tests/model-catalog.test.ts covers modelCost: each token kind, the tier threshold, and unknown prices.

Risk / rollback

Low. Every change is additive: new subpaths, one new function, and two new types. No adapter reads the catalog yet. To undo, revert the PR. Data gaps (for example 100 of 202 Bedrock models with no prices) are modelschemas gaps. Fix them upstream, and the next daily sync picks them up.

Public API change

Before

// No runtime model data. Prices and limits came from each provider's docs.

After

import { modelCost } from '@tanstack/ai'
import { models } from '@tanstack/ai-anthropic/models'

const model = models.find((m) => m.id === 'claude-sonnet-5-5')
model?.contextWindow // 1000000
const cost = model ? modelCost(model, usage) : undefined // { total, currency, ... } or undefined

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on: maintainer The ball is in the maintainers’ court

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants